<!DOCTYPE html>
<html lang="zh-cn">
<head>
    <meta charset="UTF-8">
    <title>奴隶主计算器</title>
    <style type="text/css">
        .debug {
            color: purple;
            font-style: italic;
        }

        .disabled {
            opacity: 0.35;
        }

        .card {
            width: 89px;
            height: 125.3px;
            vertical-align: middle
        }

        .card_small {
            width: 59.3px;
            height: 83.5px;
            vertical-align: middle
        }

        .mana {
            width: 30px;
            height: 30px;
            vertical-align: middle
        }

        .mana_small {
            width: 20px;
            height: 20px;
            vertical-align: middle
        }

        .hr {
            height: 1px;
            border-top: 1px solid black;
            margin-top: 5px;
            margin-bottom: 3px;
        }

        .health {
            height: 23.3px;
            width: 16.7px;
            vertical-align: middle
        }

        .armor {
            height: 21.7px;
            width: 18.3px;
            vertical-align: middle
        }

        .spacer {
            width: 15px;
            display: inline-block
        }

        .hidden {
            display: none
        }

        .success {
            font-weight: bold;
            color: green;
        }

        .hotkey {
            white-space: nowrap;
            border-right: 1px solid #999999;
            width: 180px;
            display: inline-block;
            margin-right: 8px;
        }

        .card_div {
            display: inline-block;
            position: relative;
            vertical-align: middle;
        }

        .mana_cost_div {
            position: absolute;
            top: 10px;
            left: 4px;
            font-weight: bold;
            font-size: 20px;
            background-color: #0000aa;
            display: inline-block;
            padding-left: 5px;
            padding-right: 5px;
        }

        .mana_cost_div_small {
            position: absolute;
            top: 6.7px;
            left: 2.7px;
            font-weight: bold;
            font-size: 13.3px;
            background-color: #0000aa;
            display: inline-block;
            padding-left: 3.3px;
            padding-right: 3.3px;
        }

        .attack_div {
            position: absolute;
            bottom: 0px;
            left: 4px;
            font-weight: bold;
            font-size: 20px;
            background-color: #888800;
            display: inline-block;
            padding-left: 5px;
            padding-right: 5px;
        }

        .attack_div_small {
            position: absolute;
            bottom: 0px;
            left: 2.7px;
            font-weight: bold;
            font-size: 13.3px;
            background-color: #888800;
            display: inline-block;
            padding-left: 3.3px;
            padding-right: 3.3px;
        }

        .health_div {
            position: absolute;
            bottom: 0px;
            right: 4px;
            font-weight: bold;
            font-size: 20px;
            background-color: #660000;
            display: inline-block;
            padding-left: 5px;
            padding-right: 5px;
        }

        .health_div_small {
            position: absolute;
            bottom: 0px;
            right: 2.7px;
            font-weight: bold;
            font-size: 13.3px;
            background-color: #660000;
            display: inline-block;
            padding-left: 3.3px;
            padding-right: 3.3px;
        }

        .green {
            color: #00ff00
        }

        .red {
            color: #ff0000
        }

        .white {
            color: #ffffff;
            font-weight: bold
        }

        .failure {
            color: red;
            font-weight: bold
        }

        .label {
            width: 60px;
            display: inline-block;
            text-align: right;
            padding-right: 5px;
        }

        .blackbg {
            background-color: black;
        }

        .taunt_div {
            position: absolute;
        }

        .deathrattle_div {
            color: red;
            font-size: 90px;
            text-align: center;
            position: absolute;
            height: 100%;
            width: 100%;
            padding-top: 12px;
            text-shadow: 2px 2px 0 black;
            opacity: 0.75;
        }

        .show_helps {
            width: 30px;
            position: absolute;
            top: 40px;
            right: 10px;
        }

        .hide_it {
            display: none;
        }

        .help {
            display: none;
        }

        .help_show_it {
            display: inline;
            font-style: italic;
        }

        .green_hotkey {
            color: #00a000;
            text-decoration: underline;
        }

        .red_hotkey {
            color: #ee0000;
            text-decoration: underline;
        }

        .blue_hotkey {
            color: #0000ff;
            text-decoration: underline;
        }

        .black_hotkey {
            text-decoration: underline;
        }

        .show_secrets_etc {
            position: absolute;
            top: 40px;
            right: 50px;
        }

        .secrets_etc_hidden {
            display: none;
        }

        .show_it {
            display: inline;
        }

        .go_button {
            width: 160px;
        }
    </style>
</head>
<body style="font-family:arial,sans-serif,sans" onload="bodyonload();"><b>奴隶主计算器</b>

<div class="hr"></div>
<br>
敌方血量:<span class="spacer"></span><input id="enemy_health_input" style="width:30px" placeholder="30"> <span  class="help">(默认30点血. 别忘了加入护甲. 血量越低, 计算速度越快.)</span><br><br>

<div class="hr"></div>
<br>
<input type="button" class="show_secrets_etc" value="设置奥秘和血量" onclick="show_secrets_etc(this);">
<input type="button" class="show_helps" value="?" onclick="show_helps_clicked(this);">
<span class="secrets_etc_hidden">
敌方奥秘:<span class="spacer"></span><input id="enemy_secrets_input" onchange="enemy_secrets_changed(this)"
                                                 value=""><br>
<span id="enemy_secrets_display"></span>
<br><br>
<span class="hotkey"><b class="green_hotkey">F</b>冰冻陷阱</span>
<span class="hotkey"><b class="green_hotkey">E</b>爆炸陷阱</span>
<span class="hotkey"><b class="green_hotkey">M</b>镜像实体</span>
<span class="hotkey"><b class="green_hotkey">S</b>毒蛇陷阱</span>
<br><br>
<div class="hr"></div><br>
</span>
敌方场地:<span class="spacer"></span><input id="enemy_board_input" onchange="enemy_board_changed(this)" value=""><span class="spacer"></span><input type="checkbox" checked="1"id="sort_enemy_board"> 无视位置 <span class="help">(运算速度更快)</span><br>
<span id="enemy_board_display"></span>
<br><br>
<span class="hotkey"><b class="red_hotkey">D</b>崩盘领主</span>
<!-- <span class=hotkey><b class=red_hotkey>B</b>oom Bot</span> -->
<span class="hotkey"><b class="red_hotkey">E</b>自爆绵羊</span>
<span class="hotkey"><b class="red_hotkey">H</b>鬼灵蜘蛛</span>
<span class="hotkey"><b class="red_hotkey">S</b>董大师</span>
<span class="hotkey"><b class="red_hotkey">U</b>满山食尸鬼</span>
<span class="hotkey"><b class="red_hotkey">A</b>和珅</span>
<span class="hotkey"><b class="red_hotkey">G</b>奴隶主</span>
<span class="hotkey"><b class="red_hotkey">I</b>小鬼首领</span>
<span class="hotkey"><b class="red_hotkey">v</b>狮子</span>
<span class="hotkey"><b class="red_hotkey">N</b>蜘蛛卵</span>
<span class="hotkey"><b class="red_hotkey">T</b>任何嘲讽怪</span>
<span class="hotkey"><b class="red_hotkey">Z</b>任何随从</span>

<div style="height:15px"></div>
<span class="hotkey"><b class="red_hotkey">+</b> 加嘲讽</span>
<span class="hotkey"><b class="red_hotkey">_</b> 隐藏</span>
<span class="hotkey"><b class="red_hotkey">.</b> 圣盾</span>
<span class="help"><br><br>只输入一个数字仅仅来替换随从的血量，用两个数字来表示随从的攻击力和血量，大写字母表示已经死亡，在字母后面可以添加任何状态【嘲讽，圣盾与隐藏】z+可以直接写成t【都表示嘲讽随从】，举例:14z+_14Z.d5d_a+28D33t </span>
<br>
<br>

<div class="hr"></div>
<br>
你的场地:<span class="spacer"></span><input id="board_input" onchange="board_changed(this)" value=""><span
        class="spacer"></span><input type="checkbox" checked="1" id="sort_board"> 忽略顺序 <span class="help">(速度更快)</span><span
        class="spacer"></span><input type="button" value="重置亡语顺序"
                                     onclick="reset_deathrattle_ordering();"> <span class="help">(点击（自己）对方的随从或者自己的武器可以设置顺序.)</span><br>
<span id="board_display"></span>
<br><br>
<span class="hotkey"><b class="blue_hotkey">G</b>奴隶主</span>
<span class="hotkey"><b class="blue_hotkey">C</b>战歌</span>
<span class="hotkey"><b class="blue_hotkey">B</b>暴露狂战士</span>
<span class="hotkey"><b class="blue_hotkey">A</b>和珅</span>
<span class="hotkey"><b class="blue_hotkey">Z</b> 任何随从</span>
<span class="help">
<br><br>
字母前用一个数字来替代血量；用两个数字来替代攻击力和血量. 大写的随从表示不能攻击 (被冰冻了或者刚刚召唤).  举例: cb2o33aG </span>

<br>
<br>

<div class="hr"></div>
<br>
武器:<span class="spacer"></span><input id="weapon_input" onchange="weapon_changed(this)" value=""><br>
<span id="weapon_display"></span>
<br><br>
<span class="hotkey"><b class="black_hotkey">F</b>小斧子</span>
<span class="hotkey"><b class="black_hotkey">D</b>死亡之咬</span>
<span class="help">
<br><br>
在字母前面加数字表示耐久. 举例+: 1d.  0表示被冻结.</span>
<br>
<br>

<div class="hr"></div>
<br>
手上:<span class="spacer"></span><input id="hand_input" onchange="hand_changed(this)" value=""><br>
<span id="hand_display"></span>
<br><br>
<span class="hotkey"><b class="green_hotkey">G</b>奴隶主</span>
<span class="hotkey"><b class="green_hotkey">C</b>战歌</span>
<span class="hotkey"><b class="green_hotkey">I</b>怒火中烧</span>
<span class="hotkey"><b class="green_hotkey">W</b>旋风斩</span>
<span class="hotkey"><b class="green_hotkey">B</b>暴乱狂战士</span>
<span class="hotkey"><b class="green_hotkey">L</b>战利品收藏者</span>
<span class="hotkey"><b class="green_hotkey">F</b>小斧子</span>
<span class="hotkey"><b class="green_hotkey">D</b>死亡之咬</span>
<span class="hotkey"><b class="green_hotkey">o</b>恐怖海盗</span>
<span class="hotkey"><b class="green_hotkey">T</b>残酷的监工</span>
<span class="hotkey"><b class="green_hotkey">E</b>斩杀</span>
<span class="hotkey"><b class="green_hotkey">P</b>苦痛诗僧</span>
<span class="hotkey"><b class="green_hotkey">v</b>侏儒发明家</span>
<span class="hotkey"><b class="green_hotkey">S</b>猛击</span>
<span class="hotkey"><b class="green_hotkey">U</b>满山的食尸鬼</span>
<span class="help">
<br><br>
    在字母前面表示现在的随从费用(大帝减的) 举例: g4g5g6gg
</span>

<br>
<br>

<div class="hr"></div>
<br>
当前费用:<span class="spacer"></span>
<img src="mana_crystal.png" class="mana" alt="">
<img src="mana_crystal.png" class="mana" id="mana_2" onclick="javascript:mana_clicked(this)" alt="">
<img src="mana_crystal.png" class="mana" id="mana_3" onclick="javascript:mana_clicked(this)" alt="">
<img src="mana_crystal.png" class="mana" id="mana_4" onclick="javascript:mana_clicked(this)" alt="">
<img src="mana_crystal.png" class="mana" id="mana_5" onclick="javascript:mana_clicked(this)" alt=""><span
        class="spacer"></span>
<img src="mana_crystal.png" class="mana" id="mana_6" onclick="javascript:mana_clicked(this)" alt="">
<img src="mana_crystal.png" class="mana" id="mana_7" onclick="javascript:mana_clicked(this)" alt="">
<img src="mana_crystal.png" class="mana" id="mana_8" onclick="javascript:mana_clicked(this)" alt="">
<img src="mana_crystal.png" class="mana" id="mana_9" onclick="javascript:mana_clicked(this)" alt="">
<img src="mana_crystal.png" class="mana" id="mana_10" onclick="javascript:mana_clicked(this)" alt="">
<span class="spacer"></span><span id="mana_text">10/10</span>
<span class="secrets_etc_hidden">
<div style="height:5px"></div>
你的血量:<span class="spacer"></span><input id="health_input" style="width:30px" placeholder="30"> <span class="help">(加入你的血量，如果血量过低的话。不会进行武器攻击.)</span></span><br>
<br>

<div class="hr"></div>
<br>
<input type="button" class="go_button" value="测试" onclick="run_tests()"><br>
<input type="button" class="go_button" value="开始斩杀!" onclick="go()"><br>
<br>
<span id="result"></span>

<script>
    // 常量
    var runAll = false; // 是否运行所有测试?
    //是否显示debug信息
    var showDebugOutput = !runAll && false; //false;
    var showStates = false; // false;

    var debugDeadCards = true;

    var slowRealtimeOutput = false;
    //忽略敌方顺序
    var ignoreEnemyBoardOrdering = true;
    //忽略己方顺序
    var ignoreBoardOrdering = true;
    //可用的费
    var available_mana = 10;
    //结果的html
    var resultHtml = "";
    //下一个亡语效果
    var nextDeathrattle = 1;

    var nextid = 1;
    var nextstateid = 1;
    var enemyHealth = 30;

    var Global = function () {
        this.knownStates = [];
        this.bestState = null;
        this.enemyHealth = 30;
    }

    var TestGlobal = function () {
        this.testCount = 0;
        this.passedTestCount = 0;
    }


    //设置费用
    function mana_clicked(mana_crystal) {
        var n = parseInt(mana_crystal.id.substring(5));
        for (var i = 2; i != 11; ++i) {
            var crystal = document.getElementById("mana_" + i);
            if (i <= n)
                crystal.className = "mana";
            else
                crystal.className = "mana disabled";
        }
        var txt = document.getElementById("mana_text");
        txt.innerHTML = "" + n + "/" + n;
        available_mana = n;
    }
    function is_enabled_mana(mana_crystal) {
        return mana_crystal.className == "mana";
    }
    function contains(str, substr) {
        return str.indexOf(substr) > -1;
    }

    //敌方卡牌类型.
    var ECardType = {
        WarsongCommander: 1,
        GrimPatron: 2,
        InnerRage: 3,
        Whirlwind: 4,
        FrothingBerserker: 5,
        LootHoarder: 6,
        FieryWarAxe: 7,
        DeathsBite: 8,
        DreadCorsair: 9,
        Armorsmith: 10,
        AcolyteOfPain: 11,
        CruelTaskmaster: 12,
        Execute: 13,
        NoWeapon: 14,
        GnomishInventor: 15,
        Dude: 16,
        GrommashHellscream: 17,
        Slam: 18,
        UnstableGhoul: 19,
    };

    //对敌方的动作.
    var EMove = {
        AttackFace: 1001,
        AttackEnemy: 1002,
        Cast: 1003,
        CastOnEnemy: 1004,
        CastOnFriend: 1005,
        PlayCreature: 1006,
        PlayCreatureTargetingFriend: 1007,
        PlayCreatureTargetingEnemy: 1008,
        PlayWeapon: 1009,
        ChopFace: 1010,
        ChopEnemy: 1011,
    };

    var cardTypes = {
        "g": ECardType.GrimPatron,
        "c": ECardType.WarsongCommander,
        "i": ECardType.InnerRage,
        "w": ECardType.Whirlwind,
        "b": ECardType.FrothingBerserker,
        "l": ECardType.LootHoarder,
        "f": ECardType.FieryWarAxe,
        "d": ECardType.DeathsBite,
        "o": ECardType.DreadCorsair,
        "a": ECardType.Armorsmith,
        "p": ECardType.AcolyteOfPain,
        "t": ECardType.CruelTaskmaster,
        "e": ECardType.Execute,
        "v": ECardType.GnomishInventor,
        "z": ECardType.Dude,
        "h": ECardType.GrommashHellscream,
        "s": ECardType.Slam,
        "u": ECardType.UnstableGhoul,
    };
    var cardDefs = {};
    cardDefs[ECardType.GrimPatron] = {
        type: ECardType.GrimPatron,
        name: "grim_patron",
        manaCost: 5,
        attack: 3,
        health: 3,
        moves: [EMove.PlayCreature],
        desc: '恐怖的奴隶主',
    };
    cardDefs[ECardType.WarsongCommander] = {
        type: ECardType.WarsongCommander,
        name: "warsong_commander",
        manaCost: 3,
        attack: 2,
        health: 3,
        moves: [EMove.PlayCreature],
        desc: '战歌指挥官',
    };
    cardDefs[ECardType.InnerRage] = {
        type: ECardType.InnerRage,
        name: "inner_rage",
        manaCost: 0,
        moves: [EMove.CastOnEnemy, EMove.CastOnFriend],
        desc: '怒火中烧',
    };
    cardDefs[ECardType.Slam] = {
        type: ECardType.Slam,
        name: "slam",
        manaCost: 2,
        moves: [EMove.CastOnEnemy, EMove.CastOnFriend],
        desc: '猛击',
    };
    cardDefs[ECardType.Whirlwind] = {
        type: ECardType.Whirlwind,
        name: "whirlwind",
        manaCost: 1,
        moves: [EMove.Cast],
        desc: '旋风斩',
    };
    cardDefs[ECardType.FrothingBerserker] = {
        type: ECardType.FrothingBerserker,
        name: "frothing_berserker",
        manaCost: 3,
        attack: 2,
        health: 4,
        moves: [EMove.PlayCreature],
        desc: "暴露狂战士",
    };
    cardDefs[ECardType.UnstableGhoul] = {
        type: ECardType.UnstableGhoul,
        name: "unstable_ghoul",
        manaCost: 2,
        attack: 1,
        health: 3,
        moves: [EMove.PlayCreature],
        desc: "满山的食尸鬼",
    };
    cardDefs[ECardType.GrommashHellscream] = {
        type: ECardType.GrommashHellscream,
        name: "grommash",
        manaCost: 8,
        attack: 4,
        health: 10,
        moves: [EMove.PlayCreature],
        desc: "吼爹",
    };
    cardDefs[ECardType.LootHoarder] = {
        type: ECardType.LootHoarder,
        name: "loot_hoarder",
        manaCost: 2,
        attack: 2,
        health: 1,
        moves: [EMove.PlayCreature],
        desc: "战利品贮藏家",
        dude: true,
    };
    cardDefs[ECardType.FieryWarAxe] = {
        type: ECardType.FieryWarAxe,
        name: "fiery_war_axe",
        manaCost: 2,
        attack: 3,
        health: 2,
        moves: [EMove.PlayWeapon],
        desc: "小斧子",
    };
    cardDefs[ECardType.DeathsBite] = {
        type: ECardType.DeathsBite,
        name: "deaths_bite",
        manaCost: 4,
        attack: 4,
        health: 2,
        moves: [EMove.PlayWeapon],
        desc: "死亡之咬",
    };
    cardDefs[ECardType.DreadCorsair] = {
        type: ECardType.DreadCorsair,
        name: "dread_corsair",
        manaCost: 4,
        attack: 3,
        health: 3,
        moves: [EMove.PlayCreature],
        desc: "恐怖海盗",
        dude: true,
    };
    cardDefs[ECardType.Armorsmith] = {
        type: ECardType.Armorsmith,
        name: "armorsmith",
        manaCost: 2,
        attack: 1,
        health: 4,
        moves: [EMove.PlayCreature],
        desc: "和珅",
    };
    cardDefs[ECardType.AcolyteOfPain] = {
        type: ECardType.AcolyteOfPain,
        name: "acolyte_of_pain",
        manaCost: 3,
        attack: 1,
        health: 3,
        moves: [EMove.PlayCreature],
        desc: "苦痛诗僧",
        dude: true,
    };
    cardDefs[ECardType.GnomishInventor] = {
        type: ECardType.GnomishInventor,
        name: "gnomish_inventor",
        manaCost: 4,
        attack: 2,
        health: 4,
        moves: [EMove.PlayCreature],
        desc: "侏儒发明家",
        dude: true,
    };
    cardDefs[ECardType.CruelTaskmaster] = {
        type: ECardType.CruelTaskmaster,
        name: "cruel_taskmaster",
        manaCost: 2,
        attack: 2,
        health: 2,
        moves: [EMove.PlayCreatureTargetingFriend, EMove.PlayCreatureTargetingEnemy],
        desc: "残酷的监工",
        dude: true,
    };
    cardDefs[ECardType.Execute] = {
        type: ECardType.Execute,
        name: "execute",
        manaCost: 1,
        moves: [EMove.CastOnEnemy],
        desc: "斩杀",
    };
    cardDefs[ECardType.Dude] = {
        type: ECardType.Dude,
        name: "dude",
        attack: 1,
        health: 1,
        moves: [],
        desc: "普通随从",
    };
    cardDefs[ECardType.NoWeapon] = {
        type: ECardType.NoWeapon,
        name: "no_weapon",
        manaCost: -1,
        attack: 0,
        health: -1,
        moves: [],
        desc: "<没有武器>",
    };

    var EEnemySecretType = {
        ExplosiveTrap: 2001,
        FreezingTrap: 2002,
        SnakeTrap: 2003,
        MirrorEntity: 2004,
    };

    var enemySecretTypes = {
        "e": EEnemySecretType.ExplosiveTrap,
        "f": EEnemySecretType.FreezingTrap,
        "s": EEnemySecretType.SnakeTrap,
        "m": EEnemySecretType.MirrorEntity,
    };
    var enemySecretDefs = {};
    enemySecretDefs[EEnemySecretType.ExplosiveTrap] = {
        type: EEnemySecretType.ExplosiveTrap,
        name: "explosive_trap",
        desc: "爆炸陷阱",
    };
    enemySecretDefs[EEnemySecretType.FreezingTrap] = {
        type: EEnemySecretType.FreezingTrap,
        name: "freezing_trap",
        desc: "冰冻陷阱",
    };
    enemySecretDefs[EEnemySecretType.SnakeTrap] = {
        type: EEnemySecretType.SnakeTrap,
        name: "snake_trap",
        desc: "毒蛇陷阱",
    };
    enemySecretDefs[EEnemySecretType.MirrorEntity] = {
        type: EEnemySecretType.MirrorEntity,
        name: "mirror_entity",
        desc: "镜像实体",
    };

    var EEnemyCardType = {
        Dude: 3001,
        Taunt: 3002,
        Deathlord: 3003,
        BoomBot: 3004,
        ExplosiveSheep: 3005,
        HauntedCreeper: 3006,
        SludgeBelcher: 3007,
        UnstableGhoul: 3008,
        Armorsmith: 3009,
        GrimPatron: 3010,
        ImpGangBoss: 3011,
        SavannahHighmane: 3012,
        NerubianEgg: 3013,
    };
    var enemyCardTypes = {
        "t": EEnemyCardType.Taunt,
        "z": EEnemyCardType.Dude,
        "d": EEnemyCardType.Deathlord,
        "b": EEnemyCardType.BoomBot,
        "e": EEnemyCardType.ExplosiveSheep,
        "h": EEnemyCardType.HauntedCreeper,
        "s": EEnemyCardType.SludgeBelcher,
        "u": EEnemyCardType.UnstableGhoul,
        "a": EEnemyCardType.Armorsmith,
        "g": EEnemyCardType.GrimPatron,
        "i": EEnemyCardType.ImpGangBoss,
        "v": EEnemyCardType.SavannahHighmane,
        "n": EEnemyCardType.NerubianEgg,
    };
    var enemyCardDefs = {};
    enemyCardDefs[EEnemyCardType.Taunt] = {
        type: EEnemyCardType.Taunt,
        name: "senjin_shieldmasta",
        attack: 1,
        health: 1,
        taunt: true,
        rattle: false,
        desc: "嘲讽随从",
    };
    enemyCardDefs[EEnemyCardType.Dude] = {
        type: EEnemyCardType.Dude,
        name: "dude",
        attack: 1,
        health: 1,
        taunt: false,
        rattle: false,
        desc: "普通随从",
    };
    enemyCardDefs[EEnemyCardType.Deathlord] = {
        type: EEnemyCardType.Deathlord,
        name: "deathlord",
        attack: 2,
        health: 8,
        taunt: true,
        rattle: true,
        desc: "崩盘领主",
    };
    enemyCardDefs[EEnemyCardType.BoomBot] = {
        type: EEnemyCardType.BoomBot,
        name: "boom_bot",
        attack: 1,
        health: 1,
        taunt: false,
        rattle: true,
        desc: "喷喷机器人",
    };
    enemyCardDefs[EEnemyCardType.Armorsmith] = {
        type: EEnemyCardType.Armorsmith,
        name: "armorsmith",
        attack: 1,
        health: 4,
        taunt: false,
        rattle: false,
        desc: "和珅",
    };
    enemyCardDefs[EEnemyCardType.ExplosiveSheep] = {
        type: EEnemyCardType.ExplosiveSheep,
        name: "explosive_sheep",
        attack: 1,
        health: 1,
        taunt: false,
        rattle: true,
        desc: "自爆绵羊",
    };
    enemyCardDefs[EEnemyCardType.HauntedCreeper] = {
        type: EEnemyCardType.HauntedCreeper,
        name: "haunted_creeper",
        attack: 1,
        health: 2,
        taunt: false,
        rattle: true,
        desc: "鬼灵蜘蛛",
    };
    enemyCardDefs[EEnemyCardType.SludgeBelcher] = {
        type: EEnemyCardType.SludgeBelcher,
        name: "sludge_belcher",
        attack: 3,
        health: 5,
        taunt: true,
        rattle: true,
        desc: "董大师",
    };
    enemyCardDefs[EEnemyCardType.UnstableGhoul] = {
        type: EEnemyCardType.UnstableGhoul,
        name: "unstable_ghoul",
        attack: 1,
        health: 3,
        taunt: true,
        rattle: true,
        desc: "满山的食尸鬼",
    };
    enemyCardDefs[EEnemyCardType.GrimPatron] = {
        type: EEnemyCardType.GrimPatron,
        name: "grim_patron",
        attack: 3,
        health: 3,
        taunt: false,
        rattle: false,
        desc: "恐怖的奴隶主",
    };
    enemyCardDefs[EEnemyCardType.ImpGangBoss] = {
        type: EEnemyCardType.ImpGangBoss,
        name: "imp_gang_boss",
        attack: 2,
        health: 4,
        taunt: false,
        rattle: false,
        desc: "小鬼首领",
    };
    enemyCardDefs[EEnemyCardType.SavannahHighmane] = {
        type: EEnemyCardType.SavannahHighmane,
        name: "savannah_highmane",
        attack: 6,
        health: 5,
        taunt: false,
        rattle: true,
        desc: "狮子",
    };
    enemyCardDefs[EEnemyCardType.NerubianEgg] = {
        type: EEnemyCardType.NerubianEgg,
        name: "nerubian_egg",
        attack: 0,
        health: 2,
        taunt: false,
        rattle: true,
        desc: "蜘蛛卵",
    };

    function enemy_secrets_changed(enemy_secrets_input) {
        document.getElementById('enemy_secrets_display').innerHTML
                = enemySecretsToHtml(parseEnemySecrets(enemy_secrets_input.value));
    }

    function enemy_board_changed(enemy_board_input) {
        document.getElementById('enemy_board_display').innerHTML
                = enemyBoardToHtml(parseEnemyBoard(enemy_board_input.value));
    }

    function board_changed(board_input) {
        document.getElementById('board_display').innerHTML
                = boardToHtml(parseBoard(board_input.value));
    }

    function weapon_changed(weapon_input) {
        document.getElementById('weapon_display').innerHTML
                = weaponToHtml(parseWeapon(weapon_input.value));
    }

    function hand_changed(hand_input) {
        document.getElementById('hand_display').innerHTML
                = handToHtml(parseHand(hand_input.value));
    }

    function enemySecretsToHtml(enemySecrets, styleOpt) {
        var result_html = "";
        styleOpt = styleOpt ? "_" + styleOpt : "";
        for (var i = 0; i < enemySecrets.length; ++i)
            result_html += '<div class=card_div><img src="' + enemySecretDefs[enemySecrets[i].type].name + '.png" class="card' + styleOpt + '" alt=""></div>';
        return result_html;
    }


    function enemyBoardToHtml(enemyCards, styleOpt) {
        var result_html = "";
        var useRattles = !styleOpt;
        styleOpt = styleOpt ? "_" + styleOpt : "";
        for (var i = 0; i < enemyCards.length; ++i) {
            result_html += '<div class=card_div';
            if (enemyCards[i].untargetable) result_html += ' style="opacity:0.7"';
            result_html += '>';
            if (enemyCards[i].taunt) result_html += '<div class=taunt_div><img src="taunt_shield.png" class="card' + styleOpt + '" alt=""></div>';
            if (enemyCards[i].bubble) result_html += '<div class=taunt_div><img src="bubble.png" class="card' + styleOpt + '" alt=""></div>';
            if (useRattles) result_html += '<div class=deathrattle_div onclick="add_to_deathrattle_order(this);" id=e' + i + '></div>';
            result_html += '<img src="' + enemyCardDefs[enemyCards[i].type].name + '.png" class="card' + styleOpt + '" alt="">';
            var base_attack = enemyCardDefs[enemyCards[i].type].attack;
            var color = base_attack == enemyCards[i].attack ? 'white' : (base_attack > enemyCards[i].attack ? 'red' : 'green');
            result_html += '<div class="attack_div' + styleOpt + ' ' + color + '">' + enemyCards[i].attack + '</div>';
            var base_health = enemyCards[i].origHealth;
            var color = base_health == enemyCards[i].health ? 'white' : (base_health > enemyCards[i].health ? 'red' : 'green');
            result_html += '<div class="health_div' + styleOpt + ' ' + color + '">' + enemyCards[i].health + '</div>';
            result_html += '</div>';
        }
        return result_html;
    }

    function boardToHtml(board, styleOpt) {
        var result_html = "";
        styleOpt = styleOpt ? "_" + styleOpt : "";
        for (var i = 0; i < board.length; ++i) {
            var attacked = board[i].alreadyAttacked ? " blackbg" : "";
            result_html += '<div class=card_div><img src="' + cardDefs[board[i].type].name + '.png" class="card' + styleOpt + '" alt="">';
            var base_attack = cardDefs[board[i].type].attack;
            var color = base_attack == board[i].attack ? 'white' : (base_attack > board[i].attack ? 'red' : 'green');
            result_html += '<div class="attack_div' + styleOpt + ' ' + color + attacked + '">' + board[i].attack + '</div>';
            var base_health = cardDefs[board[i].type].health;
            var color = base_health == board[i].health ? 'white' : (base_health > board[i].health ? 'red' : 'green');
            result_html += '<div class="health_div' + styleOpt + ' ' + color + attacked + '">' + board[i].health + '</div>';
            result_html += '</div>';
        }
        return result_html;
    }

    function weaponToHtml(weapon, styleOpt) {
        var result_html = "";
        var useRattles = !styleOpt;
        styleOpt = styleOpt ? "_" + styleOpt : "";
        if (!weapon || weapon.type == ECardType.NoWeapon) return result_html;

        result_html += '<div class=card_div>';
        if (useRattles) result_html += '<div class=deathrattle_div id=wdr onclick="add_to_deathrattle_order(this);"></div>';
        result_html += '<img src="' + cardDefs[weapon.type].name + '.png" class="card' + styleOpt + '" alt="">';
        var base_attack = cardDefs[weapon.type].attack;
        var color = base_attack == weapon.attack ? 'white' : (base_attack > weapon.attack ? 'red' : 'green');
        result_html += '<div class="attack_div' + styleOpt + ' ' + color + '">' + weapon.attack + '</div>';
        var base_health = cardDefs[weapon.type].health;
        var color = base_health == weapon.health ? 'white' : (base_health > weapon.health ? 'red' : 'green');
        result_html += '<div class="health_div' + styleOpt + ' ' + color + '">' + weapon.health + '</div>';
        result_html += '</div>';

        return result_html;
    }

    function handToHtml(hand, styleOpt) {
        var result_html = "";
        styleOpt = styleOpt ? "_" + styleOpt : "";
        for (var i = 0; i < hand.length; ++i) {
            result_html += '<div class=card_div><img src="' + cardDefs[hand[i].type].name + '.png" class="card' + styleOpt + '" alt="">';
            var base_mana_cost = cardDefs[hand[i].type].manaCost;
            var color = base_mana_cost == hand[i].manaCost ? 'white' : (base_mana_cost > hand[i].manaCost ? 'green' : 'red');
            result_html += '<div class="mana_cost_div' + styleOpt + ' ' + color + '">' + hand[i].manaCost + '</div>';
            result_html += '</div>';
        }
        return result_html;
    }

    var Card = function (i, c, h, a) {
        this.id = i;
        this.type = c;
        this.health = h;
        this.attack = a;
        this.alreadyAttacked = false;
        this.hits = 0; // temp, tracks hits taken for armorsmith etc
        this.deathrattleOrder = 9999;
        this.taunt = false;
        this.untargetable = false;
        this.bubble = false;
    }

    Card.prototype.clone = function () {
        var card = new Card(this.id, this.type, this.health, this.attack);
        card.alreadyAttacked = this.alreadyAttacked;
        card.origHealth = this.origHealth;
        card.deathrattleOrder = this.deathrattleOrder;
        card.taunt = this.taunt;
        card.untargetable = this.untargetable;
        card.bubble = this.bubble;
        return card;
    }

    function compareCards(a, b) {
        return a.alreadyAttacked - b.alreadyAttacked || a.type - b.type || a.health - b.health || a.attack - b.attack || a.taunt - b.taunt || a.bubble - b.bubble || a.untargetable - b.untargetable;
    }

    Card.prototype.equals = function (card) {
        return this.health == card.health && this.attack == card.attack && this.type == card.type && this.alreadyAttacked == card.alreadyAttacked && this.taunt == card.taunt && this.untargetable == card.untargetable && this.bubble == card.bubble;
    }

    Card.prototype.incompleteEquals = function (card) {
        return this.health == card.health && this.attack == card.attack && this.taunt == card.taunt && this.untargetable == card.untargetable && this.bubble == card.bubble;
    }

    Card.prototype.toHtml = function () {
        var s = (this.bubble ? "圣盾 " : "") + (this.untargetable ? " 潜行 " : "") + (this.taunt ? " 嘲讽 " : "") + this.attack + "/" + this.health + " "
                + (this.type < 1000 ? cardDefs[this.type].desc : enemyCardDefs[this.type].desc);
        return this.alreadyAttacked ? "<s>" + s + "</s>" : s;
    }


    var CardInHand = function (i, c, m) {
        this.id = i;
        this.type = c;
        this.manaCost = m;
    };

    CardInHand.prototype.clone = function () {
        return new CardInHand(this.id, this.type, this.manaCost);
    }

    CardInHand.prototype.equals = function (card) {
        return this.manaCost == card.manaCost && this.type == card.type;
    }

    CardInHand.prototype.incompleteEquals = function (card) {
        return this.manaCost == card.manaCost;
    }

    CardInHand.prototype.toHtml = function () {
        return this.manaCost + " 费 " + cardDefs[this.type].desc;
    }

    CardInHand.prototype.lessThan = function (card) {
        return this.type < card.type
                || this.type == card.type && (this.manaCost < card.manaCost
                );
    }

    function compareCardsInHand(a, b) {
        return a.type - b.type || a.manaCost - b.manaCost;
    }


    var EnemySecretCard = function (i, c) {
        this.id = i;
        this.type = c;
    };

    EnemySecretCard.prototype.clone = function () {
        return new EnemySecretCard(this.id, this.type);
    }

    EnemySecretCard.prototype.equals = function (card) {
        return this.type == card.type;
    }

    EnemySecretCard.prototype.lessThan = function (card) {
        return this.type < card.type;
    }

    function compareEnemySecrets(a, b) {
        return a.type - b.type;
    }

    EnemySecretCard.prototype.toHtml = function () {
        return enemySecretDefs[this.type].desc;
    }

    function parseThisLetter(this_letter) {
        if (this_letter >= '0' && this_letter <= '9')
            return this_letter - '0';
        switch (this_letter) {
            case '!':
                return 11;
            case '@':
                return 12;
            case '#':
                return 13;
            case '$':
                return 14;
            case '%':
                return 15;
            case '^':
                return 16;
            case '&':
                return 17;
            case '*':
                return 18;
            case '(':
                return 19;
            case ')':
                return 10;
        }
        return -1;
    }
    function parseEnemySecrets(enemy_secret_input_str) {
        var enemySecrets = [];
        for (var i = 0; i != enemy_secret_input_str.length; ++i) {
            var this_letter = enemy_secret_input_str.charAt(i).toLowerCase();

            var cardType = enemySecretTypes[this_letter];
            if (cardType != undefined)
                enemySecrets.push(new EnemySecretCard(nextid++, cardType));
        }
        return enemySecrets;
    }


    function parseEnemyBoard(enemy_board_input_str) {
        var enemyBoard = [];
        for (var i = 0; i != enemy_board_input_str.length; ++i) {
            var this_letter = enemy_board_input_str.charAt(i);
            var health = parseThisLetter(this_letter);
            var attack = -1;
            if (health >= 0) {
                if (i != enemy_board_input_str.length - 1) {
                    ++i;
                    this_letter = enemy_board_input_str.charAt(i);
                    var this_number = parseThisLetter(this_letter);
                    if (this_number >= 0) {
                        attack = health;
                        health = this_number;
                        if (i != enemy_board_input_str.length - 1) {
                            ++i;
                            this_letter = enemy_board_input_str.charAt(i);
                        }
                    }
                }
            }

            var plusTaunt = false;
            var untargetable = false;
            var bubble = false;
            var tryAgain = true;
            while (tryAgain && i != enemy_board_input_str.length - 1) {
                ++i;
                switch (enemy_board_input_str.charAt(i)) {
                    case '+':
                        plusTaunt = true;
                        break;
                    case '_':
                        untargetable = true;
                        break;
                    case '.':
                        bubble = true;
                        break;
                    default:
                        --i;
                        tryAgain = false;
                }
            }

            var lc_letter = this_letter.toLowerCase();
            var enemyCardType = enemyCardTypes[lc_letter];
            if (enemyCardType != undefined) {
                var card = new Card(nextid++, enemyCardType,
                        health >= 0 ? health : enemyCardDefs[enemyCardType].health,
                        attack >= 0 ? attack : enemyCardDefs[enemyCardType].attack);
                card.origHealth = lc_letter == this_letter ? card.health : card.health + 1;
                card.taunt = enemyCardDefs[enemyCardType].taunt || plusTaunt;
                card.untargetable = untargetable;
                card.bubble = bubble;
                enemyBoard.push(card);
            }
        }
        return enemyBoard;
    }

    function parseBoard(board_input_str) {
        var board = [];
        for (var i = 0; i != board_input_str.length; ++i) {
            var this_letter = board_input_str.charAt(i);
            var health = parseThisLetter(this_letter);
            var attack = -1;
            if (health >= 0) {
                if (i != board_input_str.length - 1) {
                    ++i;
                    this_letter = board_input_str.charAt(i);
                    var this_number = parseThisLetter(this_letter);
                    if (this_number >= 0) {
                        attack = health;
                        health = this_number;
                        if (i != board_input_str.length - 1) {
                            ++i;
                            this_letter = board_input_str.charAt(i);
                        }
                    }
                }
            }

            var lc_letter = this_letter.toLowerCase();
            var cardType = cardTypes[lc_letter];
            if (cardType != undefined) {
                var card = new Card(nextid++, cardType,
                        health >= 0 ? health : cardDefs[cardType].health,
                        attack >= 0 ? attack : cardDefs[cardType].attack);
                if (lc_letter != this_letter) card.alreadyAttacked = true;
                board.push(card);
            }
        }
        return board;
    }

    function parseWeapon(weapon_input_str) {
        var weapon = new Card(nextid++, ECardType.NoWeapon, 1, 0);
        if (weapon_input_str.length <= 0) return weapon;

        var this_letter = weapon_input_str.charAt(0);
        var health = -1;
        if (this_letter >= '0' && this_letter <= '9') {
            health = this_letter - '0';
            if (weapon_input_str.length > 1) {
                this_letter = weapon_input_str.charAt(1);
            }
        }

        var lc_letter = this_letter.toLowerCase();
        var cardType = cardTypes[lc_letter];
        if (cardType != undefined) {
            weapon.type = cardType;
            weapon.health = health >= 0 ? health : cardDefs[cardType].health;
            weapon.attack = cardDefs[cardType].attack;
            if (lc_letter != this_letter) weapon.alreadyAttacked = true;
        }
        return weapon;
    }
    function parseHand(hand_input_str) {
        var hand = [];
        for (var i = 0; i != hand_input_str.length; ++i) {
            var this_letter = hand_input_str.charAt(i);
            var this_number = -1;
            if (this_letter >= '0' && this_letter <= '9') {
                this_number = this_letter - '0';
                if (i != hand_input_str.length - 1) {
                    ++i;
                    this_letter = hand_input_str.charAt(i);
                }
            }

            var cardType = cardTypes[this_letter];
            if (cardType != undefined)
                hand.push(new CardInHand(nextid++, cardType,
                        this_number >= 0 ? this_number : cardDefs[cardType].manaCost));
        }
        return hand;
    }


    ////////////////////////////////

    // todo:
    // someday:
    // - add boom bot enemy type. requires forks based on all possible boomed targets but which one results in least (worst) damage, but would support sylvanas too

    var State = function (inEnemySecrets, inEnemyBoardState, inBoardState, inWeapon, inHandState, inMana, inDamage) {
        this.enemySecrets = inEnemySecrets;
        this.enemyBoard = inEnemyBoardState;
        this.board = inBoardState;
        this.weapon = inWeapon;
        this.hand = inHandState;
        this.mana = inMana;
        this.damage = inDamage;
        this.moves = [];
        this.id = -1;
        this.attackedWithWeapon = false;
        this.moveCount = 0; // not moves.length; it's the # moves taken to reach this state.
        this.health = 30;
    }

    State.prototype.clone = function () {
        var newEnemySecrets = [];
        var newEnemyBoard = [];
        var newBoard = [];
        var newHand = [];
        for (var i = 0; i != this.enemySecrets.length; ++i)
            newEnemySecrets.push(this.enemySecrets[i].clone());
        for (var i = 0; i != this.enemyBoard.length; ++i)
            newEnemyBoard.push(this.enemyBoard[i].clone());
        for (var i = 0; i != this.board.length; ++i)
            newBoard.push(this.board[i].clone());
        for (var i = 0; i != this.hand.length; ++i)
            newHand.push(this.hand[i].clone());
        var newState = new State(newEnemySecrets, newEnemyBoard, newBoard, this.weapon.clone(), newHand, this.mana, this.damage);
        newState.health = this.health;
        newState.attackedWithWeapon = this.attackedWithWeapon;
        newState.moveCount = this.moveCount;

        return newState;
    }

    State.prototype.sort = function () {
        this.hand.sort(compareCardsInHand);
        this.enemySecrets.sort(compareEnemySecrets);
        if (ignoreBoardOrdering) this.board.sort(compareCards);
        if (ignoreEnemyBoardOrdering) this.enemyBoard.sort(compareCards);
    }

    State.prototype.canCharge = function (card) {
        if (card.attack > 3) return false;
        for (var i = 0; i < this.board.length; ++i)
            if (this.board[i].type == ECardType.WarsongCommander)
                return true;
        return false;
    }

    State.prototype.whirlwind = function (amount) {
        for (var i = 0; i < this.enemyBoard.length; ++i)
            this.hit(this.enemyBoard[i], amount);
        for (var i = 0; i < this.board.length; ++i)
            this.hit(this.board[i], amount);
    }

    State.prototype.hit = function (card, amount) {
        if (card.bubble)
            card.bubble = false;
        else {
            card.health -= amount;
            ++card.hits;
        }
    }

    State.prototype.cleanTheDead = function () {
        var enemyArmorsmiths = 0;
        for (var i = 0; i < this.enemyBoard.length; ++i)
            if (this.enemyBoard[i].type == EEnemyCardType.Armorsmith)
                ++enemyArmorsmiths;
        for (var i = 0; i < this.enemyBoard.length; ++i) {
            if (this.enemyBoard[i].hits == 0) continue;
            this.damage -= enemyArmorsmiths;
            for (var j = 0; j < this.board.length; ++j)
                if (this.board[j].type == ECardType.FrothingBerserker)
                    ++this.board[j].attack;
            if (this.enemyBoard[i].type == EEnemyCardType.GrimPatron) {
                if (this.enemyBoard.length < 7 && this.enemyBoard[i].health >= 1)
                    this.enemyBoard.push(new Card(nextid++, EEnemyCardType.GrimPatron, 3, 3));
            }
            else if (this.enemyBoard[i].type == EEnemyCardType.ImpGangBoss) {
                if (this.enemyBoard.length < 7)
                    this.enemyBoard.push(new Card(nextid++, EEnemyCardType.Dude, 1, 1));
            }
            this.enemyBoard[i].hits = 0;
        }
        var haveWarsongCommander = false;
        for (var i = 0; i < this.board.length; ++i)
            if (this.board[i].type == ECardType.WarsongCommander) {
                haveWarsongCommander = true;
                break;
            }
        var armorsmiths = 0;
        for (var i = 0; i < this.board.length; ++i)
            if (this.board[i].type == ECardType.Armorsmith)
                ++armorsmiths;
        for (var i = 0; i < this.board.length; ++i) {
            if (this.board[i].hits == 0) continue;
            this.health += armorsmiths;
            for (var j = 0; j < this.board.length; ++j)
                if (this.board[j].type == ECardType.FrothingBerserker)
                    ++this.board[j].attack;
            if (this.board[i].type == ECardType.GrimPatron && this.board.length < 7 && this.board[i].health >= 1) {
                var card = new Card(nextid++, ECardType.GrimPatron, 3, 3);
                card.alreadyAttacked = !haveWarsongCommander;
                this.board.push(card);
            }
            if (this.board[i].type == ECardType.GrommashHellscream && this.board[i].health == this.board[i].origHealth) {
                this.board[i].attack += 6;
                this.board[i].alreadyAttacked = false;
            }
            this.board[i].hits = 0;
        }
        var deadCards = [];
        for (var i = 0; i < this.enemyBoard.length; ++i) {
            if (this.enemyBoard[i].health <= 0) {
                if (enemyCardDefs[this.enemyBoard[i].type].rattle) {
                    var insertAt = 0;
                    for (; insertAt < deadCards.length && deadCards[insertAt].deathrattleOrder < this.enemyBoard[i].deathrattleOrder; ++insertAt);
                    deadCards.splice(insertAt, 0, this.enemyBoard[i]);
                }
                this.enemyBoard.splice(i--, 1);
            }
        }
        for (var i = 0; i < this.board.length; ++i) {
            if (this.board[i].health <= 0) {
                if (this.board[i].type == ECardType.UnstableGhoul) {
                    var insertAt = 0;
                    for (; insertAt < deadCards.length && deadCards[insertAt].deathrattleOrder < this.board[i].deathrattleOrder; ++insertAt);
                    deadCards.splice(insertAt, 0, this.board[i]);
                }
                this.board.splice(i--, 1);
            }
        }

        if (this.weapon.health <= 0) {
            if (this.weapon.type == ECardType.DeathsBite) {
                var insertAt = 0;
                for (; insertAt < deadCards.length && deadCards[insertAt].deathrattleOrder < this.weapon.deathrattleOrder; ++insertAt);
                deadCards.splice(insertAt, 0, this.weapon);
            }
            this.weapon = new Card(nextid++, ECardType.NoWeapon, 1, 0);
        }

        this.doDeathrattles(deadCards, haveWarsongCommander);
    }

    State.prototype.doDeathrattles = function (deadCards, haveWarsongCommander) {
        if (debugDeadCards && deadCards.length > 1) {
            var s = "<span class=label>Death rattle order:</span>";
            for (var i = 0; i < deadCards.length; ++i) {
                s += '<div class=card_div><img src="';
                if (deadCards[i].type == ECardType.DeathsBite) s += "deaths_bite";
                else if (deadCards[i].type == ECardType.UnstableGhoul) s += "unstable_ghoul";
                else s += enemyCardDefs[deadCards[i].type].name;
                s += '.png" class="card_small" alt=""></div>' + deadCards[i].deathrattleOrder + ' ';
            }
            dbg(s);
        }
        var needCleanup = false;
        for (var i = 0; i < deadCards.length; ++i) {
            if (deadCards[i].type == ECardType.DeathsBite || deadCards[i].type == EEnemyCardType.UnstableGhoul || deadCards[i].type == ECardType.UnstableGhoul) {
                this.whirlwind(1);
                needCleanup = true;
            }
            else if (deadCards[i].type == EEnemyCardType.HauntedCreeper) {
                if (this.enemyBoard.length < 7) this.enemyBoard.push(new Card(nextid++, EEnemyCardType.Dude, 1, 1));
                if (this.enemyBoard.length < 7) this.enemyBoard.push(new Card(nextid++, EEnemyCardType.Dude, 1, 1));
            }
            else if (deadCards[i].type == EEnemyCardType.SavannahHighmane) {
                if (this.enemyBoard.length < 7) this.enemyBoard.push(new Card(nextid++, EEnemyCardType.Dude, 2, 2));
                if (this.enemyBoard.length < 7) this.enemyBoard.push(new Card(nextid++, EEnemyCardType.Dude, 2, 2));
            }
            else if (deadCards[i].type == EEnemyCardType.NerubianEgg) {
                if (this.enemyBoard.length < 7) this.enemyBoard.push(new Card(nextid++, EEnemyCardType.Dude, 4, 4));
            }
            else if (deadCards[i].type == EEnemyCardType.SludgeBelcher) {
                if (this.enemyBoard.length < 7) {
                    var card = new Card(nextid++, EEnemyCardType.Taunt, 2, 1);
                    card.taunt = true;
                    this.enemyBoard.push(card);
                }
            }
            else if (deadCards[i].type == EEnemyCardType.ExplosiveSheep) {
                this.whirlwind(2);
                needCleanup = true;
            }
            else if (deadCards[i].type == EEnemyCardType.Deathlord) {
                if (this.board.length < 7) {
                    var card = new Card(nextid++, ECardType.Dude, 3, 1);
                    if (haveWarsongCommander === undefined) {
                        var haveWarsongCommander = false;
                        for (var i = 0; i < this.board.length; ++i)
                            if (this.board[i].type == ECardType.WarsongCommander) {
                                haveWarsongCommander = true;
                                break;
                            }
                    }
                    card.alreadyAttacked = !haveWarsongCommander;
                    this.board.push(card);
                }
            }
        }
        if (needCleanup)
            this.cleanTheDead();
    }

    State.prototype.findOnEnemySecrets = function (enemyCard) {
        for (var i = 0; i != this.enemySecrets.length; ++i)
            if (this.enemySecrets[i].equals(enemyCard))
                return i;
    }

    State.prototype.findOnEnemyBoard = function (enemyCard) {
        for (var i = 0; i != this.enemyBoard.length; ++i)
            if (this.enemyBoard[i].equals(enemyCard))
                return i;
    }

    State.prototype.findOnBoard = function (card) {
        for (var i = 0; i != this.board.length; ++i)
            if (this.board[i].equals(card))
                return i;
    }

    State.prototype.findInHand = function (card) {
        for (var i = 0; i != this.hand.length; ++i)
            if (this.hand[i].equals(card))
                return i;
    }

    State.prototype.incompleteEquals = function (state) {
        if (!state.weapon.equals(this.weapon)) return false;
        for (var i = 0; i < state.enemySecrets.length; ++i)
            if (!state.enemySecrets[i].equals(this.enemySecrets[i])) return false;
        for (var i = 0; i < state.enemyBoard.length; ++i)
            if (!state.enemyBoard[i].incompleteEquals(this.enemyBoard[i])) return false;
        for (var i = 0; i < state.board.length; ++i)
            if (!state.board[i].incompleteEquals(this.board[i])) return false;
        for (var i = 0; i < state.hand.length; ++i)
            if (!state.hand[i].incompleteEquals(this.hand[i])) return false;

        return true;
    }

    State.prototype.equals = function (state) {
        if (state.mana != this.mana) return false;
        if (state.damage != this.damage) return false;
        if (state.health != this.health) return false;
        if (state.hand.length != this.hand.length) return false;
        if (state.board.length != this.board.length) return false;
        if (state.enemyBoard.length != this.enemyBoard.length) return false;
        if (state.enemySecrets.length != this.enemySecrets.length) return false;
        if (!state.weapon.equals(this.weapon)) return false;
        for (var i = 0; i < state.enemySecrets.length; ++i)
            if (!state.enemySecrets[i].equals(this.enemySecrets[i])) return false;
        for (var i = 0; i < state.enemyBoard.length; ++i)
            if (!state.enemyBoard[i].equals(this.enemyBoard[i])) return false;
        for (var i = 0; i < state.board.length; ++i)
            if (!state.board[i].equals(this.board[i])) return false;
        for (var i = 0; i < state.hand.length; ++i)
            if (!state.hand[i].equals(this.hand[i])) return false;

        return true;
    }

    function addKnownState(global, state) {
        state.sort();

        var stateList = global.knownStates[state.damage] || (global.knownStates[state.damage] = []);
        stateList = stateList[state.mana] || (stateList[state.mana] = []);
        stateList = stateList[state.hand.length] || (stateList[state.hand.length] = []);
        stateList = stateList[state.board.length] || (stateList[state.board.length] = []);
        stateList = stateList[state.enemyBoard.length] || (stateList[state.enemyBoard.length] = []);
        stateList = stateList[state.health] || (stateList[state.health] = []);
        stateList = stateList[state.enemySecrets.length] || (stateList[state.enemySecrets.length] = []);

        for (var i = 0; i < state.board.length; ++i) {
            var card = state.board[i];
            stateList = stateList[card.type] || (stateList[card.type] = []);
            stateList = stateList[card.alreadyAttacked] || (stateList[card.alreadyAttacked] = []);
        }
        for (var i = 0; i < state.enemyBoard.length; ++i) {
            var card = state.enemyBoard[i];
            stateList = stateList[card.type] || (stateList[card.type] = []);
        }
        for (var i = 0; i < state.hand.length; ++i) {
            var card = state.hand[i];
            stateList = stateList[card.type] || (stateList[card.type] = []);
        }

        stateList.push(state);
    }

    function findKnownState(global, state) {
        state.sort();

        var stateList = global.knownStates[state.damage];
        if (!stateList) return undefined;
        stateList = stateList[state.mana];
        if (!stateList) return undefined;
        stateList = stateList[state.hand.length];
        if (!stateList) return undefined;
        stateList = stateList[state.board.length];
        if (!stateList) return undefined;
        stateList = stateList[state.enemyBoard.length];
        if (!stateList) return undefined;
        stateList = stateList[state.health];
        if (!stateList) return undefined;
        stateList = stateList[state.enemySecrets.length];
        if (!stateList) return undefined;

        for (var i = 0; i < state.board.length; ++i) {
            var card = state.board[i];
            stateList = stateList[card.type];
            if (!stateList) return undefined;
            stateList = stateList[card.alreadyAttacked];
            if (!stateList) return undefined;
        }
        for (var i = 0; i < state.enemyBoard.length; ++i) {
            var card = state.enemyBoard[i];
            stateList = stateList[card.type];
            if (!stateList) return undefined;
        }
        for (var i = 0; i < state.hand.length; ++i) {
            var card = state.hand[i];
            stateList = stateList[card.type];
            if (!stateList) return undefined;
        }

        for (var i = 0; i < stateList.length; ++i)
            if (stateList[i].incompleteEquals(state))
                return stateList[i];
    }

    State.prototype.triggerSnakeTrap = function () {
        for (var i = 0; i < this.enemySecrets.length; ++i) {
            if (this.enemySecrets[i].type == EEnemySecretType.SnakeTrap) {
                this.enemySecrets.splice(i, 1);
                for (var j = 0; j != 3; ++j)
                    if (this.enemyBoard.length < 7) this.enemyBoard.push(new Card(nextid++, EEnemyCardType.Dude, 1, 1));
                return;
            }
        }
    }

    State.prototype.triggerExplosiveTrap = function () {
        for (var i = 0; i < this.enemySecrets.length; ++i) {
            if (this.enemySecrets[i].type == EEnemySecretType.ExplosiveTrap) {
                this.enemySecrets.splice(i, 1);
                this.health -= 2;
                for (var j = 0; j < this.board.length; ++j)
                    this.hit(this.board[j], 2);
                this.cleanTheDead();
                return true;
            }
        }
        return false;
    }
    State.prototype.triggerFreezingTrap = function (iBoard) {
        for (var i = 0; i < this.enemySecrets.length; ++i) {
            if (this.enemySecrets[i].type == EEnemySecretType.FreezingTrap) {
                this.enemySecrets.splice(i, 1);
                this.board.splice(iBoard, 1);
                return true;
            }
        }
        return false;
    }

    State.prototype.calculateMoves = function (global) {
        if (global.bestState.damage >= global.enemyHealth) return;
        if (this.moves.length > 0) return;
        dbg("Calculating moves for state " + this.id);
        var tauntExists = false;
        for (var i = 0; i != this.enemyBoard.length && !tauntExists && i != 7; ++i)
            tauntExists = this.enemyBoard[i].taunt;

        for (var i = 0; i < this.board.length; ++i) {
            if (this.board[i].attack == 0 || this.board[i].alreadyAttacked) continue;
            for (var j = 0; j < this.enemyBoard.length; ++j)
                if (global.bestState.damage < global.enemyHealth && (!tauntExists || this.enemyBoard[j].taunt))
                    this.moves.push(new Move(global, this, EMove.AttackEnemy, this.board[i], this.enemyBoard[j]));

            if (!tauntExists && global.bestState.damage < global.enemyHealth)
                this.moves.push(new Move(global, this, EMove.AttackFace, this.board[i]));
        }

        if (this.weapon.type != ECardType.NoWeapon && this.weapon.health > 0 && !this.attackedWithWeapon) {
            if (!tauntExists && global.bestState.damage < global.enemyHealth)
                this.moves.push(new Move(global, this, EMove.ChopFace, this.weapon));

            for (var j = 0; j < this.enemyBoard.length; ++j)
                if ((!tauntExists || this.enemyBoard[j].taunt) && this.enemyBoard[j].attack < this.health && global.bestState.damage < global.enemyHealth)
                    this.moves.push(new Move(global, this, EMove.ChopEnemy, this.weapon, this.enemyBoard[j]));
        }

        for (var i = 0; i < this.hand.length; ++i) {
            if (this.hand[i].manaCost > this.mana) continue;
            var cardMoves = cardDefs[this.hand[i].type].moves;
            for (var j = 0; j < cardMoves.length; ++j) {
                switch (cardMoves[j]) {
                    case EMove.PlayCreature:
                        if (this.board.length >= 7) continue;
                        if (global.bestState.damage < global.enemyHealth) this.moves.push(new Move(global, this, cardMoves[j], this.hand[i]));
                        break;
                    case EMove.PlayWeapon:
                        if (global.bestState.damage < global.enemyHealth) this.moves.push(new Move(global, this, cardMoves[j], this.hand[i]));
                        break;
                    case EMove.CastOnEnemy:
                        var isExecute = this.hand[i].type == ECardType.Execute;
                        for (var k = 0; k < this.enemyBoard.length; ++k) {
                            if (isExecute && (this.enemyBoard[k].untargetable || !this.enemyBoard[k].taunt || this.enemyBoard[k].health >= this.enemyBoard[k].origHealth)) continue;
                            if (global.bestState.damage < global.enemyHealth) this.moves.push(new Move(global, this, cardMoves[j], this.hand[i], this.enemyBoard[k]));
                        }
                        break;
                    case EMove.CastOnFriend:
                        for (var k = 0; k < this.board.length; ++k)
                            if (global.bestState.damage < global.enemyHealth) this.moves.push(new Move(global, this, cardMoves[j], this.hand[i], this.board[k]));
                        break;
                    case EMove.Cast:
                        if (global.bestState.damage < global.enemyHealth) this.moves.push(new Move(global, this, cardMoves[j], this.hand[i]));
                        break;
                    case EMove.PlayCreatureTargetingFriend:
                        if (this.board.length >= 7) continue;
                        for (var k = 0; k < this.board.length; ++k)
                            if (global.bestState.damage < global.enemyHealth) this.moves.push(new Move(global, this, cardMoves[j], this.hand[i], this.board[k]));
                        break;
                    case EMove.PlayCreatureTargetingEnemy:
                        if (this.board.length >= 7) continue;
                        for (var k = 0; k < this.enemyBoard.length; ++k)
                            if (global.bestState.damage < global.enemyHealth) this.moves.push(new Move(global, this, cardMoves[j], this.hand[i], this.enemyBoard[k]));
                        break;
                }
            }
        }

        addKnownState(global, this);
    };

    var Move = function (global, inState, inMoveType, inSourceCard, inTargetCard) {
        this.type = inMoveType;
        this.source = inSourceCard.clone();
        this.priorState = inState;
        if (inTargetCard != undefined)
            this.target = inTargetCard.clone();
        this.state = inState.clone();
        var iSource;
        var iTarget;
        var resultSource;
        var resultTarget;

        switch (this.type) {
            case EMove.AttackFace:
                iSource = this.state.findOnBoard(this.source);
                resultSource = this.state.board[iSource];
                break;
            case EMove.AttackEnemy:
                iSource = this.state.findOnBoard(this.source);
                resultSource = this.state.board[iSource];
                iTarget = this.state.findOnEnemyBoard(this.target);
                resultTarget = this.state.enemyBoard[iTarget];
                break;
            case EMove.CastOnEnemy:
                iSource = this.state.findInHand(this.source);
                resultSource = this.state.hand[iSource];
                iTarget = this.state.findOnEnemyBoard(this.target);
                resultTarget = this.state.enemyBoard[iTarget];
                break;
            case EMove.CastOnFriend:
                iSource = this.state.findInHand(this.source);
                resultSource = this.state.hand[iSource];
                iTarget = this.state.findOnBoard(this.target);
                resultTarget = this.state.board[iTarget];
                break;
            case EMove.Cast:
                iSource = this.state.findInHand(this.source);
                resultSource = this.state.hand[iSource];
                break;
            case EMove.PlayCreatureTargetingEnemy:
                iTarget = this.state.findOnEnemyBoard(this.target);
                resultTarget = this.state.enemyBoard[iTarget];
                iSource = this.state.findInHand(this.source);
                resultSource = new Card(this.source.id, cardDefs[this.state.hand[iSource].type].dude ? ECardType.Dude : this.state.hand[iSource].type, cardDefs[this.state.hand[iSource].type].health, cardDefs[this.state.hand[iSource].type].attack);
                break;
            case EMove.PlayCreatureTargetingFriend:
                iTarget = this.state.findOnBoard(this.target);
                resultTarget = this.state.board[iTarget];
            case EMove.PlayWeapon:
            case EMove.PlayCreature:
                iSource = this.state.findInHand(this.source);
                resultSource = new Card(this.source.id, cardDefs[this.state.hand[iSource].type].dude ? ECardType.Dude : this.state.hand[iSource].type, cardDefs[this.state.hand[iSource].type].health, cardDefs[this.state.hand[iSource].type].attack);
                break;
            case EMove.ChopEnemy:
                iTarget = this.state.findOnEnemyBoard(this.target);
                resultTarget = this.state.enemyBoard[iTarget];
            case EMove.ChopFace:
                this.state.attackedWithWeapon = true;
                break;
        }
        dbg("Possible move for state " + inState.id + ": " + this.toHtml());

        if (this.type == EMove.AttackFace) {
            var cancelAttack = false;
            if (this.state.triggerExplosiveTrap())
                cancelAttack = resultSource.health <= 0;
            if (!cancelAttack)
                cancelAttack = this.state.triggerFreezingTrap(iSource);
            if (!cancelAttack) {
                this.state.damage += this.source.attack;
                resultSource.alreadyAttacked = true;
            }
        }
        else if (this.type == EMove.AttackEnemy) {
            this.state.triggerSnakeTrap();
            this.state.hit(resultTarget, this.source.attack);
            this.state.hit(resultSource, this.target.attack);
            resultSource.alreadyAttacked = true;
        }
        else if (this.type == EMove.Cast) {
            this.state.mana -= this.source.manaCost;
            this.state.whirlwind(1); // only possibility
            this.state.hand.splice(iSource, 1);
        }
        else if (this.type == EMove.CastOnEnemy) {
            this.state.mana -= this.source.manaCost;
            switch (this.source.type) {
                case ECardType.InnerRage:
                    this.state.hit(resultTarget, 1);
                    resultTarget.attack += 2;
                    break;
                case ECardType.Slam:
                    this.state.hit(resultTarget, 2);
                    break;
                case ECardType.Execute:
                    this.state.enemyBoard.splice(iTarget, 1);
                    if (enemyCardDefs[this.target.type].rattle) this.state.doDeathrattles([this.target]);
                    break;
            }
            this.state.hand.splice(iSource, 1);
        }
        else if (this.type == EMove.CastOnFriend) {
            this.state.mana -= this.source.manaCost;
            switch (this.source.type) {
                case ECardType.Slam:
                    this.state.hit(resultTarget, 2);
                    break;
                case ECardType.InnerRage:
                    this.state.hit(resultTarget, 1);
                    resultTarget.attack += 2;
                    break;
            }
            this.state.hand.splice(iSource, 1);
        }
        else if (this.type == EMove.PlayCreature) {
            if (!this.state.canCharge(resultSource))
                resultSource.alreadyAttacked = true;
            var manaCost = this.source.manaCost;
            if (this.source.type == ECardType.DreadCorsair) {
                manaCost -= this.state.weapon.attack;
                if (manaCost < 0) manaCost = 0;
            }
            this.state.mana -= manaCost;
            this.state.board.push(resultSource);
            this.state.hand.splice(iSource, 1);
        }
        else if (this.type == EMove.PlayCreatureTargetingFriend) {
            if (!this.state.canCharge(resultSource))
                resultSource.alreadyAttacked = true;
            this.state.mana -= this.source.manaCost;
            this.state.board.push(resultSource);
            switch (this.source.type) {
                case ECardType.CruelTaskmaster:
                    this.state.hit(resultTarget, 1);
                    resultTarget.attack += 2;
                    break;
            }
            this.state.hand.splice(iSource, 1);
        }
        else if (this.type == EMove.PlayCreatureTargetingEnemy) {
            if (!this.state.canCharge(resultSource))
                resultSource.alreadyAttacked = true;
            this.state.mana -= this.source.manaCost;
            this.state.board.push(resultSource);
            switch (this.source.type) {
                case ECardType.CruelTaskmaster:
                    this.state.hit(resultTarget, 1);
                    resultTarget.attack += 2;
                    break;
            }
            this.state.hand.splice(iSource, 1);
        }
        else if (this.type == EMove.PlayWeapon) {
            this.state.mana -= this.source.manaCost;
            if (this.state.weapon.type == ECardType.DeathsBite)
                this.state.whirlwind(1);
            this.state.weapon = resultSource;
            ++this.state.weapon.deathrattleOrder;
            this.state.hand.splice(iSource, 1);
        }
        else if (this.type == EMove.ChopFace) {
            this.state.triggerExplosiveTrap();
            this.state.damage += this.source.attack;
            --this.state.weapon.health;
        }
        else if (this.type == EMove.ChopEnemy) {
            this.state.triggerSnakeTrap();
            this.state.hit(resultTarget, this.state.weapon.attack);
            this.state.health -= this.target.attack;
            --this.state.weapon.health;
        }
        this.state.cleanTheDead();

        var knownState = findKnownState(global, this.state);
        if (knownState) {
            this.state = knownState;
            dbg("Resulting in an existing state " + this.state.id);
            return;
        }

        this.state.id = nextstateid++;
        this.state.priorMove = this;
        ++this.state.moveCount;
        if (this.state.damage > global.bestState.damage || this.state.damage == global.bestState.damage && (this.state.moveCount < global.bestState.moveCount || this.state.moveCount == global.bestState.moveCount && this.state.mana > global.bestState.mana))
            global.bestState = this.state;
        dbg("Resulting in a new state: " + this.state.toHtml());
        this.state.calculateMoves(global);
    }

    Move.prototype.toHtml = function () {
        var s = "<b><span style=\"color:#008800\">";
        switch (this.type) {
            case EMove.ChopFace:
            case EMove.AttackFace:
                s +=  "使用"+ this.source.toHtml()+"攻击敌方英雄 ";
                break;
            case EMove.ChopEnemy:
            case EMove.AttackEnemy:
                s += "使用" +this.source.toHtml() + "攻击" +  this.target.toHtml();
                break;
            case EMove.Cast:
                s += "使用" + this.source.toHtml();
                break;
            case EMove.CastOnEnemy:
                s +=+ " 对敌方 "  +this.target.toHtml() +"使用 " + this.source.toHtml() ;
                break;
            case EMove.CastOnFriend:
                s +=  " 对己方 " + this.target.toHtml()+ "使用 " + this.source.toHtml();
                break;
            case EMove.PlayWeapon:
            case EMove.PlayCreature:
                s += "使用" + this.source.toHtml();
                break;
            case EMove.PlayCreatureTargetingFriend:
                s += "使用 " + this.source.toHtml() + " 对己方随从 " + this.target.toHtml();
                break;
            case EMove.PlayCreatureTargetingEnemy:
                s += "使用 " + this.source.toHtml() + " 对敌方随从 " + this.target.toHtml();
                break;
        }
        return s + "</b></span>";
    }

    State.prototype.toHtml = function () {
        var html_result = 'State ' + this.id + ':<br>';
        if (this.enemySecrets.length > 0) html_result += "<span class=label>奥秘:</span>" + enemySecretsToHtml(this.enemySecrets, 'small') + "<br>";
        html_result += "<span class=label>地方:</span>" + enemyBoardToHtml(this.enemyBoard, 'small') + "<br>";
        html_result += "<span class=label>面板:</span>" + boardToHtml(this.board, 'small') + "<br>";
        html_result += "<span class=label>武器:</span>" + weaponToHtml(this.weapon, 'small') + "<br>";
        html_result += "<span class=label>手牌:</span>" + handToHtml(this.hand, 'small') + "<br>";
        html_result += "<span class=label>费用:</span>";
        for (var i = 0; i != this.mana && i != 10; ++i) {
            html_result += '<img src="mana_crystal.png" class="mana_small" alt="">';
        }
        html_result += ' (' + this.mana + "), 伤害: " + this.damage;
        if (this.health != 30) html_result += ", 血量: " + this.health;
        return html_result + "<br><br>";
    }

    function go() {
        var global = new Global();
        nextid = 1;
        nextstateid = 1;
        ignoreEnemyBoardOrdering = document.getElementById('sort_enemy_board').checked;
        ignoreBoardOrdering = document.getElementById('sort_board').checked;
        document.getElementById('result').innerHTML = "";
        resultHtml = "";

        var state = new State(
                parseEnemySecrets(document.getElementById('enemy_secrets_input').value),
                parseEnemyBoard(document.getElementById('enemy_board_input').value),
                parseBoard(document.getElementById('board_input').value),
                parseWeapon(document.getElementById('weapon_input').value),
                parseHand(document.getElementById('hand_input').value),
                available_mana, 0
        );
        state.id = nextstateid++;
        var inHealth = parseInt(document.getElementById('health_input').value);
        if (!isNaN(inHealth)) state.health = inHealth;
        var inEnemyHealth = parseInt(document.getElementById('enemy_health_input').value);
        if (!isNaN(inEnemyHealth)) global.enemyHealth = inEnemyHealth;

        var elems = document.getElementsByTagName('div');
        for (var i = 0; i < elems.length; ++i) {
            if (elems[i].className == 'deathrattle_div' && elems[i].innerHTML) {
                if (elems[i].id == 'wdr')
                    state.weapon.deathrattleOrder = elems[i].innerHTML;
                else {
                    var iEnemyBoard = parseInt(elems[i].id.substring(1, elems[i].id.length));
                    state.enemyBoard[iEnemyBoard].deathrattleOrder = elems[i].innerHTML;
                }
            }
        }
        global.bestState = state;
        dbg("Starting state: " + state.toHtml());
        state.calculateMoves(global);
        printBestPlay(global);
        document.getElementById('result').innerHTML = resultHtml;
        resultHtml = "";
    }

    function printBestPlay(global) {
        var thisMove = global.bestState.priorMove;
        var s = "";
        while (thisMove) {
            s = thisMove.toHtml() + "<br>" + (showStates ? "计算结果： " + thisMove.state.toHtml() : "") + s;
            thisMove = thisMove.priorState.priorMove;
        }
        if (!showStates) s += "<br>伤害: " + global.bestState.damage;
        print("斩杀顺序:<br>" + s);
    }

    function makeState(stateHash) {
        var state = new State(
                parseEnemySecrets(stateHash['secrets'] ? stateHash['secrets'] : ""),
                parseEnemyBoard(stateHash['enemy'] ? stateHash['enemy'] : ""),
                parseBoard(stateHash['board'] ? stateHash['board'] : ""),
                parseWeapon(stateHash['weapon'] ? stateHash['weapon'] : ""),
                parseHand(stateHash['hand'] ? stateHash['hand'] : ""),
                stateHash['mana'] ? stateHash['mana'] : 10,
                stateHash['dmg'] ? stateHash['dmg'] : 0
        );
        state.id = nextstateid++;
        if (!isNaN(stateHash['health'])) state.health = stateHash['health'];
        return state;
    }

    function add_to_deathrattle_order(div) {
        div.innerHTML = nextDeathrattle++;
    }
    function reset_deathrattle_ordering() {
        var elems = document.getElementsByTagName('div');
        for (var i = 0; i < elems.length; ++i)
            if (elems[i].className == 'deathrattle_div')
                elems[i].innerHTML = '';
        nextDeathrattle = 1;
    }

    function show_helps_clicked(showHelpsButton) {
        showHelpsButton.className = 'hide_it';
        var elems = document.getElementsByTagName('span');
        for (var i = 0; i < elems.length; ++i)
            if (elems[i].className == 'help')
                elems[i].className = 'help_show_it';
    }

    function show_secrets_etc(showButton) {
        showButton.className = 'hide_it';
        var elems = document.getElementsByTagName('span');
        for (var i = 0; i < elems.length; ++i)
            if (elems[i].className == 'secrets_etc_hidden')
                elems[i].className = 'show_it';
    }

    function run_tests() {
        nextid = 1;
        nextstateid = 1;
        document.getElementById('result').innerHTML = "";
        resultHtml = "";
        var testGlobal = new TestGlobal();
        ignoreEnemyBoardOrdering = true;
        ignoreBoardOrdering = true;
        if (runAll) doTest(testGlobal, "Attack face when no taunts are up<br>Your creature ignores dude<br>Damage is added when creature on your board hits face<br>Face attacker can no longer attack",
                {board: "21z", enemy: "92z"},
                {board: "21Z", enemy: "92z", dmg: 2});
        if (runAll) doTest(testGlobal, "Attack taunts before face<br>Minimal damage used to kill taunts<br>Taunts deal damage to attacking minions",
                {board: "21za", enemy: "11t"},
                {board: "21Z13A", dmg: 2, health: 31});
        if (runAll) doTest(testGlobal, "Don't play a creature if it's pointless<br>0 damage on face max results in no play<br>Summoning sickness can't attack",
                {hand: "a"},
                {hand: "a"});
        if (runAll) doTest(testGlobal, "Playing creatures from hand reduces mana<br>Warsong commander first<br>Warsong commander grants small minions charge",
                {hand: "ac"},
                {board: "CA", mana: 5, dmg: 1});
        if (runAll) doTest(testGlobal, "Equip best weapon<br>Weapon costs mana<br>Ignore non-taunt<br>Can't attack more than once per turn<br>Fewest moves possible",
                {hand: "fd", enemy: "14z"},
                {hand: "f", enemy: "14z", weapon: "1d", dmg: 4, mana: 6});
        if (runAll) doTest(testGlobal, "Can't play creature if 7 on board",
                {board: "ccccccc", hand: "bb"},
                {board: "CCCCCCC", hand: "bb", dmg: 14});
        if (runAll) doTest(testGlobal, "Plays stop when running out of mana<br>Commander grants charge",
                {board: "c", hand: "bbpp", mana: 8},
                {board: "CBB", hand: "pp", dmg: 6, mana: 2});
        if (runAll) doTest(testGlobal, "Cruel taskmaster works when played on friend<br>Frothing berserker gains attack when damaged",
                {board: "b13z", hand: "t"},
                {board: "53B13Z22Z", mana: 8, dmg: 6});
        if (runAll) doTest(testGlobal, "Cruel taskmaster on enemy to clear a taunt",
                {board: "55b", enemy: "11t", hand: "t"},
                {board: "65B22Z", mana: 8, dmg: 6});
        if (runAll) doTest(testGlobal, "Inner rage works on friend and enemy",
                {board: "13z", enemy: "11t", hand: "ii"},
                {board: "32Z", dmg: 3});
        if (runAll) doTest(testGlobal, "play uncharged creatures to gain WW bonus<br>whirlwind works",
                {enemy: "33z", board: "b", hand: "aw"},
                {enemy: "32z", board: "53B13A", mana: 7, dmg: 5, health: 32});
        if (runAll) doTest(testGlobal, "overwrite deaths bite to trigger a WW for more dmg",
                {enemy: "33z", board: "b", hand: "df"},
                {enemy: "32z", board: "43B", weapon: "f", mana: 4, dmg: 8});
        if (runAll) doTest(testGlobal, "can't execute til enemy is damaged<br>execute for more dps<br>can't chop face if enemy taunt exists",
                {enemy: "15t", board: "53z", weapon: "f", hand: "e"},
                {board: "53Z", weapon: "1f", mana: 9, dmg: 5, health: 29});
        if (runAll) doTest(testGlobal, "Friendly grim patron splits off<br>new patrons cant attack without commander<br>ww doesnt hit new patrons",
                {board: "bg", hand: "w"},
                {board: "43B32G33G", mana: 9, dmg: 7});
        if (runAll) doTest(testGlobal, "Inner rage before ww on grims<br>patrons can attack with commander",
                {board: "c", hand: "igw"},
                {board: "22C51G32GGG", mana: 4, dmg: 16});
        if (runAll) doTest(testGlobal, "attack patron into dude to multiply",
                {board: "c", enemy: "11z", hand: "gw"},
                {board: "22C31G32GGG", mana: 4, dmg: 11});
        if (runAll) doTest(testGlobal, "prefer least mana spent<br>weapon attack value discounts corsair cost",
                {board: "c", hand: "fo"},
                {board: "C33Z", weapon: "1f", mana: 7, dmg: 8});
        if (runAll) doTest(testGlobal, "spiders properly split<br>deathrattles occur in correct order<br>chop spiders for more ww dmg",
                {enemy: "h", board: "bbbb", weapon: "1d"},
                {board: "93B93B93B93B", dmg: 36, health: 29},
                {enemyDeathrattleOrder: 1, weaponDeathrattleOrder: 2});
        if (runAll) doTest(testGlobal, "when haunted creeper was played after deaths bite",
                {enemy: "h", board: "bbbb", weapon: "1d"},
                {board: ")2B)3B)3B)3B)", dmg: 34},
                {enemyDeathrattleOrder: 2, weaponDeathrattleOrder: 1});
        if (runAll) doTest(testGlobal, "corsair with no weapon equipped costs 4",
                {board: "c", hand: "o"},
                {board: "C33Z", mana: 6, dmg: 5});
        if (runAll) doTest(testGlobal, "equipped deathsbite gets last deathrattle",
                {enemy: "h", hand: "fd", board: "bbbb"},
                {board: "93B93B93B93B", weapon: "f", mana: 4, dmg: 36, health: 29});
        if (runAll) doTest(testGlobal, "enemy belcher",
                {enemy: "s", hand: "cig"},
                {board: "C32G33G", mana: 2, dmg: 3});
        if (runAll) doTest(testGlobal, "explosive sheep",
                {enemy: "e", board: "bbbb", weapon: "1f"},
                {board: "72B72B72B72B", dmg: 28, health: 29});
        if (runAll) doTest(testGlobal, "death lord<br>execute triggers deathrattle<br>commander charge works with executed deathrattles",
                {enemy: "d", board: "cb", weapon: "1f", hand: "e"},
                {board: "21C44B13Z", mana: 9, dmg: 8});
        if (runAll) doTest(testGlobal, "unstable ghoul",
                {enemy: "u", board: "cbg"},
                {board: "21C)3B31G32GGG", dmg: 19});
        if (runAll) doTest(testGlobal, "enemy armorsmith",
                {enemy: "sa", hand: "cig"},
                {enemy: "a", board: "C32G33G", mana: 2, dmg: 1});
        if (runAll) doTest(testGlobal, "enemies damage you when you chop them",
                {enemy: "53t", board: "82b82b", weapon: "1f"},
                {board: "92B92B", dmg: 18, health: 25});
        if (runAll) doTest(testGlobal, "dont suicide chopping enemies",
                {enemy: "53t", board: "82b82b", weapon: "1f", health: 5},
                {board: ")2B", dmg: 13, health: 5});
        if (runAll) doTest(testGlobal, "own armorsmith health gain<br>use own armorsmith to gain health required to chop enemy",
                {enemy: "53t", board: "82z82za", weapon: "1f", health: 5},
                {board: "82Z82Z", dmg: 16, health: 1});
        if (runAll) doTest(testGlobal, "taunted special cards e.g. explosive sheep",
                {enemy: "e+", board: "12z13z"},
                {board: "11Z", dmg: 1});
        if (runAll) doTest(testGlobal, "enemy grim patron",
                {enemy: "g", board: "b", hand: "ww"},
                {enemy: "31g32ggg", board: "72B", mana: 8, dmg: 7});
        if (runAll) doTest(testGlobal, "enemy imp gang boss",
                {enemy: "i", board: "b", hand: "ww"},
                {enemy: "22iz", board: "72B", mana: 8, dmg: 7});
        if (runAll) doTest(testGlobal, "enemy savannah highmane",
                {enemy: "v+", board: "53b53b"},
                {enemy: "22z22z", board: "73B", dmg: 7});
        if (runAll) doTest(testGlobal, "enemy nerubian egg",
                {enemy: "n+", board: "13z13z13z"},
                {enemy: "44z", board: "13Z13Z13Z", dmg: 1});
        if (runAll) doTest(testGlobal, "cant execute untargetables",
                {enemy: "27T_", board: "74z74z", hand: "e"},
                {board: "72Z74Z", hand: "e", dmg: 7});
        if (runAll) doTest(testGlobal, "snake trap",
                {enemy: "11z", secrets: "s", board: "33zb", hand: "w"},
                {board: "31Z93B", mana: 9, dmg: 9});
        if (runAll) doTest(testGlobal, "explosive trap<br>attacking minions killed by explosive don't do damage",
                {secrets: "e", board: "11zcg", weapon: "1f"},
                {board: "21C31GG", dmg: 11, health: 28});
        if (runAll) doTest(testGlobal, "freezing trap (face)<br>attacking minions removed by freezing dont do damage (face)<br>freeze worst minion",
                {secrets: "f", board: "23z13z"},
                {board: "23Z", dmg: 2});
        if (runAll) doTest(testGlobal, "freezing trap (enemy board)<br>attacking minions removed by freezing dont do damage (enemy board)",
                {secrets: "f", enemy: "13t", board: "33z13z33z"},
                {board: "32Z33Z", dmg: 3});
        if (runAll) doTest(testGlobal, "freezing + explosive: big minion triggers both",
                {secrets: "fe", board: "33z55z"},
                {board: "53Z", dmg: 5, health: 28});
        if (runAll) doTest(testGlobal, "bubble<br>bubbles dont grant berserkers attack<br>bubbles dont affect armorsmith",
                {enemy: "a22t.", board: "bbb"},
                {enemy: "a", board: "52B52B54B", dmg: 4});
        if (runAll) doTest(testGlobal, "gnomish inventor",
                {board: "c", hand: "v"},
                {board: "C24Z", dmg: 4, mana: 6});
        if (runAll) doTest(testGlobal, "stop at x damage",
                {enemy: "e", board: "bbbb", weapon: "1f"},
                {board: "82b81B82B82B", dmg: 16, weapon: "1f"},
                {enemyHealth: 15});
        if (runAll) doTest(testGlobal, "slam",
                {hand: "s", enemy: "22t", board: "33z"},
                {board: "33Z", mana: 8, dmg: 3});
        if (runAll) doTest(testGlobal, "friendly unstable ghoul",
                {hand: "ub", enemy: "55z", board: "c"},
                {enemy: "53z", board: "22C73B", mana: 5, dmg: 9});


        // stress tests
        if (false) doTest(testGlobal, "stress test #1",
                {enemy: "22z", hand: "4g2b2c1ti", weapon: "1d"}); // 276 MB
        // at 9 mana, 31621 unique states
        // at 9 mana, 13196 unique states (when allowed to sort boards)
        // at 9 mana, 12799 unique states (after dudification)
        // unimplemented:
        //if (false) doTest(testGlobal, "grommash",
        //  {hand:"hi"},
        //  {board:"@9H", mana:1, dmg:12});

        print("<div class=hr></div>");
        print("All tests complete. " + testGlobal.testCount + " tests ran, " + testGlobal.passedTestCount + " passed, " + (testGlobal.testCount - testGlobal.passedTestCount) + " failed.");
        print("Final result: <span class=" + (testGlobal.testCount == testGlobal.passedTestCount ? "success>Passed!" : "failure>Failed") + "</span>");
        document.getElementById('result').innerHTML = resultHtml;
        resultHtml = "";
    }

    function doTest(testGlobal, testName, raw_state, raw_endState, options) {
        ++testGlobal.testCount;
        var global = new Global();
        var state = makeState(raw_state);
        var endState;
        if (raw_endState) endState = makeState(raw_endState);
        global.enemyHealth = 9999;
        if (options) {
            if (options.enemyHealth) global.enemyHealth = options.enemyHealth;
            if (options.enemyDeathrattleOrder) state.enemyBoard[0].deathrattleOrder = options.enemyDeathrattleOrder;
            if (options.weaponDeathrattleOrder) state.weapon.deathrattleOrder = options.weaponDeathrattleOrder;
        }
        global.bestState = state;
        print('<span style="color:blue;font-weight:bold">Running the following test(s):</span>');
        print(testName + "<br>");
        dbg("Starting state: " + state.toHtml());
        state.calculateMoves(global);
        printBestPlay(global);
        if (!endState) {
            ++testGlobal.passedTestCount;
            print("<span class=success>Passed!</span><br><br>");
            return;
        }
        endState.sort();
        dbg("Expected final state: " + endState.toHtml());
        print("");

        if (global.bestState.equals(endState)) {
            ++testGlobal.passedTestCount;
            print("<span class=success>Passed!</span>");
        }
        else
            print("<span class=failure>Failed</span>");

        print("<br><br>");
    }


    function dbg(s) {
        if (!showDebugOutput) return;
        if (slowRealtimeOutput) document.getElementById('result').innerHTML += "<span class=debug>" + s + "</span><br>";
        else resultHtml += "<span class=debug>" + s + "</span><br>";
    }

    function print(s) {
        if (slowRealtimeOutput) document.getElementById('result').innerHTML += s + "<br>";
        else resultHtml += s + "<br>";
    }

    function bodyonload() {
        document.getElementById('enemy_secrets_input').value = "";
        document.getElementById('enemy_board_input').value = "";
        document.getElementById('board_input').value = "";
        document.getElementById('hand_input').value = "";
        document.getElementById('weapon_input').value = "";
    }


</script>


</body>
</html>